//go:build linux
// +build linux

package epoll

import (
	"sync"

	"golang.org/x/sys/unix"

	"gitee.com/hasika/common-server/conn/inet"
)

type Epoll struct {
	efd         int
	connections map[int]inet.Conn
	lock        *sync.RWMutex
}

func MkEpoll() (*Epoll, error) {
	fd, err := unix.EpollCreate1(0)
	if err != nil {
		return nil, err
	}
	return &Epoll{
		efd:         fd,
		lock:        &sync.RWMutex{},
		connections: make(map[int]inet.Conn),
	}, nil
}

func (e *Epoll) Add(conn inet.Conn) error {
	fd := conn.GetFd()
	e.lock.Lock()
	defer e.lock.Unlock()
	ctlMode := unix.EPOLL_CTL_ADD
	_, ex := e.connections[fd]
	if ex {
		ctlMode = unix.EPOLL_CTL_MOD
	}
	flag := unix.EPOLLIN | unix.EPOLLET | unix.EPOLLONESHOT
	err := unix.EpollCtl(e.efd, ctlMode, fd, &unix.EpollEvent{Events: uint32(flag), Fd: int32(fd)})
	if err != nil {
		Log.Errorf("add fd %d to epoll error, %+v", fd, err)
		return err
	}
	return nil
}

func (e *Epoll) Remove(conn inet.Conn) error {
	fd := conn.GetFd()
	e.lock.Lock()
	defer e.lock.Unlock()
	_, ex := e.connections[fd]
	if !ex {
		return nil
	}
	delete(e.connections, fd)
	err := unix.EpollCtl(e.efd, unix.EPOLL_CTL_DEL, fd, nil)
	if err != nil {
		return err
	}
	return nil
}

func (e *Epoll) Wait() ([]inet.Conn, error) {
	events := make([]unix.EpollEvent, 1000)
retry:
	n, err := unix.EpollWait(e.efd, events, -1)
	if err != nil {
		if err == unix.EINTR {
			goto retry
		}
		return nil, err
	}
	e.lock.RLock()
	defer e.lock.RUnlock()
	var connections []inet.Conn
	for i := 0; i < n; i++ {
		epollEvent := events[i]
		if epollEvent.Events&unix.EPOLLIN > 0 {
			conn := e.connections[int(epollEvent.Fd)]
			connections = append(connections, conn)
		}
	}
	return connections, nil
}
